﻿using System;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace DynamicVm
{
    internal class DynamicBindableMetaObject : DynamicMetaObject
    {
        private const string GetMethodName = "GetValue";
        private const string SetMethodName = "SetValue";
        private const string InvokeMethodName = "Invoke";

        private static readonly MethodInfo GetMethod;
        private static readonly MethodInfo SetMethod;
        private static readonly MethodInfo InvokeMethod;

        static DynamicBindableMetaObject()
        {
            Type vmType = typeof (DynamicBindable);
            GetMethod = vmType.GetMethod(GetMethodName);
            SetMethod = vmType.GetMethod(SetMethodName);
            InvokeMethod = vmType.GetMethod(InvokeMethodName);
        }

        public DynamicBindableMetaObject(Expression expression, DynamicBindable value) 
            :base(expression, BindingRestrictions.Empty, value)
        {
            _restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
            _self = Expression.Convert(Expression, LimitType);
        }

        public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
        {
            var args = new Expression[]
                {
                    Expression.Constant(binder.Name),
                    Expression.Convert(value.Expression, typeof (object))
                };

            Expression call = Expression.Call(_self, SetMethod, args);

            return new DynamicMetaObject(call, _restrictions);
        }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            var args = new Expression[]
                {
                    Expression.Constant(binder.Name),
                };

            Expression call = Expression.Call(_self, GetMethod, args);

            return new DynamicMetaObject(call, _restrictions);
        }

        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            var @params = args.Select(Expression.Constant).Cast<Expression>().ToList();

            if (!@params.Any())
            {
                @params.Add(Expression.Constant(new object[0]));
            }

            @params.Insert(0, Expression.Constant(binder.Name));

            Expression call = Expression.Call(_self, InvokeMethod, @params);
            return new DynamicMetaObject(call, _restrictions);
        }

        private readonly BindingRestrictions _restrictions;
        private readonly Expression _self;

    }
}
